/*
This file is part of BGSLibrary.

BGSLibrary is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

BGSLibrary is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with BGSLibrary.  If not, see <http://www.gnu.org/licenses/>.
*/
/****************************************************************************
*
* Image.h
*
* Purpose:  C++ wrapper for OpenCV IplImage which supports simple and
*						efficient access to the image data
*
* Author: Donovan Parks, September 2007
*
* Based on code from:
*  http://www.cs.iit.edu/~agam/cs512/lect-notes/opencv-intro/opencv-intro.html
******************************************************************************/
#pragma once

#include <opencv2/opencv.hpp>
#include <cv.h>
// --- Image Iterator ---------------------------------------------------------

template <class T>
class ImageIterator
{
public:
  ImageIterator(IplImage* image, int x = 0, int y = 0, int dx = 0, int dy = 0) :
    i(x), j(y), i0(0)
  {
    data = reinterpret_cast<T*>(image->imageData);
    step = image->widthStep / sizeof(T);

    nl = image->height;
    if ((y + dy) > 0 && (y + dy) < nl)
      nl = y + dy;

    if (y < 0)
      j = 0;

    data += step*j;

    nc = image->width;
    if ((x + dx) > 0 && (x + dx) < nc)
      nc = x + dx;

    nc *= image->nChannels;
    if (x > 0)
      i0 = x*image->nChannels;
    i = i0;

    nch = image->nChannels;
  }


  /* has next ? */
  bool operator!() const { return j < nl; }

  /* next pixel */
  ImageIterator& operator++()
  {
    i++;
    if (i >= nc)
    {
      i = i0;
      j++;
      data += step;
    }
    return *this;
  }

  ImageIterator& operator+=(int s)
  {
    i += s;
    if (i >= nc)
    {
      i = i0;
      j++;
      data += step;
    }
    return *this;
  }

  /* pixel access */
  T& operator*() { return data[i]; }

  const T operator*() const { return data[i]; }

  const T neighbor(int dx, int dy) const
  {
    return *(data + dy*step + i + dx);
  }

  T* operator&() const { return data + i; }

  /* current pixel coordinates */
  int column() const { return i / nch; }
  int line() const { return j; }

private:
  int i, i0, j;
  T* data;
  int step;
  int nl, nc;
  int nch;
};

// --- Constants --------------------------------------------------------------

const unsigned char NUM_CHANNELS = 3;

// --- Pixel Types ------------------------------------------------------------

class RgbPixel
{
public:
  RgbPixel() { ; }
  RgbPixel(unsigned char _r, unsigned char _g, unsigned char _b)
  {
    ch[0] = _r; ch[1] = _g; ch[2] = _b;
  }

  RgbPixel& operator=(const RgbPixel& rhs)
  {
    ch[0] = rhs.ch[0]; ch[1] = rhs.ch[1]; ch[2] = rhs.ch[2];
    return *this;
  }

  inline unsigned char& operator()(const int _ch)
  {
    return ch[_ch];
  }

  inline unsigned char operator()(const int _ch) const
  {
    return ch[_ch];
  }

  unsigned char ch[3];
};

class RgbPixelFloat
{
public:
  RgbPixelFloat() { ; }
  RgbPixelFloat(float _r, float _g, float _b)
  {
    ch[0] = _r; ch[1] = _g; ch[2] = _b;
  }

  RgbPixelFloat& operator=(const RgbPixelFloat& rhs)
  {
    ch[0] = rhs.ch[0]; ch[1] = rhs.ch[1]; ch[2] = rhs.ch[2];
    return *this;
  }

  inline float& operator()(const int _ch)
  {
    return ch[_ch];
  }

  inline float operator()(const int _ch) const
  {
    return ch[_ch];
  }

  float ch[3];
};

// --- Image Types ------------------------------------------------------------

class ImageBase
{
public:
  ImageBase(IplImage* img = NULL) { imgp = img; m_bReleaseMemory = true; }
  ~ImageBase();

  void ReleaseMemory(bool b) { m_bReleaseMemory = b; }

  IplImage* Ptr() { return imgp; }
  const IplImage* Ptr() const { return imgp; }

  void ReleaseImage()
  {
    cvReleaseImage(&imgp);
  }

  void operator=(IplImage* img)
  {
    imgp = img;
  }

  // copy-constructor
  ImageBase(const ImageBase& rhs)
  {
    // it is very inefficent if this copy-constructor is called
    assert(false);
  }

  // assignment operator
  ImageBase& operator=(const ImageBase& rhs)
  {
    // it is very inefficent if operator= is called
    assert(false);

    return *this;
  }

  virtual void Clear() = 0;

protected:
  IplImage* imgp;
  bool m_bReleaseMemory;
};

class RgbImage : public ImageBase
{
public:
  RgbImage(IplImage* img = NULL) : ImageBase(img) { ; }

  virtual void Clear()
  {
    cvZero(imgp);
  }

  void operator=(IplImage* img)
  {
    imgp = img;
  }

  // channel-level access using image(row, col, channel)
  inline unsigned char& operator()(const int r, const int c, const int ch)
  {
    return (unsigned char &)imgp->imageData[r*imgp->widthStep + c*imgp->nChannels + ch];
  }

  inline const unsigned char& operator()(const int r, const int c, const int ch) const
  {
    return (unsigned char &)imgp->imageData[r*imgp->widthStep + c*imgp->nChannels + ch];
  }

  // RGB pixel-level access using image(row, col)
  inline RgbPixel& operator()(const int r, const int c)
  {
    return (RgbPixel &)imgp->imageData[r*imgp->widthStep + c*imgp->nChannels];
  }

  inline const RgbPixel& operator()(const int r, const int c) const
  {
    return (RgbPixel &)imgp->imageData[r*imgp->widthStep + c*imgp->nChannels];
  }
};

class RgbImageFloat : public ImageBase
{
public:
  RgbImageFloat(IplImage* img = NULL) : ImageBase(img) { ; }

  virtual void Clear()
  {
    cvZero(imgp);
  }

  void operator=(IplImage* img)
  {
    imgp = img;
  }

  // channel-level access using image(row, col, channel)
  inline float& operator()(const int r, const int c, const int ch)
  {
    return (float &)imgp->imageData[r*imgp->widthStep + (c*imgp->nChannels + ch) * sizeof(float)];
  }

  inline float operator()(const int r, const int c, const int ch) const
  {
    return (float)imgp->imageData[r*imgp->widthStep + (c*imgp->nChannels + ch) * sizeof(float)];
  }

  // RGB pixel-level access using image(row, col)
  inline RgbPixelFloat& operator()(const int r, const int c)
  {
    return (RgbPixelFloat &)imgp->imageData[r*imgp->widthStep + c*imgp->nChannels * sizeof(float)];
  }

  inline const RgbPixelFloat& operator()(const int r, const int c) const
  {
    return (RgbPixelFloat &)imgp->imageData[r*imgp->widthStep + c*imgp->nChannels * sizeof(float)];
  }
};

class BwImage : public ImageBase
{
public:
  BwImage(IplImage* img = NULL) : ImageBase(img) { ; }

  virtual void Clear()
  {
    cvZero(imgp);
  }

  void operator=(IplImage* img)
  {
    imgp = img;
  }

  // pixel-level access using image(row, col)
  inline unsigned char& operator()(const int r, const int c)
  {
    return (unsigned char &)imgp->imageData[r*imgp->widthStep + c];
  }

  inline unsigned char operator()(const int r, const int c) const
  {
    return (unsigned char)imgp->imageData[r*imgp->widthStep + c];
  }
};

class BwImageFloat : public ImageBase
{
public:
  BwImageFloat(IplImage* img = NULL) : ImageBase(img) { ; }

  virtual void Clear()
  {
    cvZero(imgp);
  }

  void operator=(IplImage* img)
  {
    imgp = img;
  }

  // pixel-level access using image(row, col)
  inline float& operator()(const int r, const int c)
  {
    return (float &)imgp->imageData[r*imgp->widthStep + c * sizeof(float)];
  }

  inline float operator()(const int r, const int c) const
  {
    return (float)imgp->imageData[r*imgp->widthStep + c * sizeof(float)];
  }
};

// --- Image Functions --------------------------------------------------------

void DensityFilter(BwImage& image, BwImage& filtered, int minDensity, unsigned char fgValue);
